package post

import (
	"context"
	"strconv"
	"strings"

	"mall/common/globalkey"
	"mall/common/modelx"
	"mall/common/utils/pointy"
	"mall/service/blog/model"
	"mall/service/blog/rpc/internal/svc"
	"mall/service/blog/rpc/internal/utils/errorhandler"
	"mall/service/blog/rpc/types/blog"

	"github.com/zeromicro/go-zero/core/logx"
)

type BackendGetPostListLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
	logx.Logger
}

func NewBackendGetPostListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BackendGetPostListLogic {
	return &BackendGetPostListLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

func (l *BackendGetPostListLogic) BackendGetPostList(in *blog.TmpPostListReq) (*blog.TmpPostListResp, error) {
	var orderBys []string
	if len(in.OrderBy) > 0 {
		for _, orderBy := range in.OrderBy {
			order := " ASC"
			if orderBy.Order == "1" {
				order = " DESC"
			}
			if orderBy.Field == "id" {
				orderBys = append(orderBys, model.BlogPostColumns.ID+order)
			}
			if orderBy.Field == "sort" {
				orderBys = append(orderBys, model.BlogPostColumns.MenuOrder+order)
			}
			if orderBy.Field == "createdAt" {
				orderBys = append(orderBys, model.BlogPostColumns.PostDate+order)
			}
		}
	}

	var stati string
	var postType string
	if len(in.FilterBy) > 0 {
		for _, filterBy := range in.FilterBy {
			switch filterBy.Field {
			case "status":
				stati = filterBy.In
			case "type":
				postType = filterBy.In
			}
		}
	}

	var page uint64
	var pageSize uint64
	var restricted bool
	var withCount int
	var preCount int64
	filterMap := make(map[string]map[string][]any)
	resp := &blog.TmpPostListResp{}
	if in.Ids != nil && len(*in.Ids) > 0 {
		if vals := modelx.UniqueInterfaces(*in.Ids, 64); len(vals) == 0 {
			return resp, nil
		} else {
			filterMap[model.BlogPostColumns.ID] = map[string][]any{
				"in": vals,
			}
			restricted = true
			withCount = -1
			preCount = int64(len(vals))
		}
	}

	var statusIsSet bool
	var isAll bool
	statusMap := make(map[string][]any)
	switch postType {
	case "1": // 全部
		statusMap["notEqualTo"] = []any{globalkey.PostStatusTrash}
		isAll = true
	case "2": // 待审核
		filterMap[model.BlogPostColumns.PostStatus] = map[string][]any{
			"equalTo": {globalkey.PostStatusPending},
		}
		statusIsSet = true
	case "3": // 已通过
		filterMap[model.BlogPostColumns.PostStatus] = map[string][]any{
			"in": {
				globalkey.PostStatusPublish,
				globalkey.PostStatusPrivate,
				globalkey.PostStatusFuture,
			},
		}
		statusIsSet = true
	case "4": // 已驳回
		filterMap[model.BlogPostColumns.PostStatus] = map[string][]any{
			"equalTo": {globalkey.PostStatusRejected},
		}
		statusIsSet = true
	case "5": // 草稿
		filterMap[model.BlogPostColumns.PostStatus] = map[string][]any{
			"equalTo": {globalkey.PostStatusDraft, globalkey.PostStatusAutoDraft},
		}
		statusIsSet = true
	case "6": // 回收站
		filterMap[model.BlogPostColumns.PostStatus] = map[string][]any{
			"equalTo": {globalkey.PostStatusTrash},
		}
		statusIsSet = true
	}

	if !restricted {
		page = in.Page
		pageSize = in.PageSize
		withCount = 1
	}

	if in.Title != nil && len(strings.TrimSpace(*in.Title)) > 0 {
		filterMap[model.BlogPostColumns.PostTitle] = map[string][]any{
			"contain": {strings.TrimSpace(*in.Title)},
		}
	}
	if in.Slug != nil && len(strings.TrimSpace(*in.Slug)) >= 2 {
		filterMap[model.BlogPostColumns.PostName] = map[string][]any{
			"contain": {strings.TrimSpace(*in.Slug)},
		}
	}
	if in.Excerpt != nil && len(strings.TrimSpace(*in.Excerpt)) > 0 {
		filterMap[model.BlogPostColumns.PostExcerpt] = map[string][]any{
			"contain": {strings.TrimSpace(*in.Excerpt)},
		}
	}

	if in.AuthorId != nil {
		filterMap[model.BlogPostColumns.PostAuthorID] = map[string][]any{
			"equalTo": {*in.AuthorId},
		}
	}
	if in.ParentId != nil {
		filterMap[model.BlogPostColumns.PostParentID] = map[string][]any{
			"equalTo": {*in.ParentId},
		}
	}

	var startTimeIn []any
	var endTimeIn []any
	if in.StartTime != nil && len(strings.TrimSpace(*in.StartTime)) > 0 {
		if startTime, err := modelx.ParseLocalDate(strings.TrimSpace(*in.StartTime)); err == nil {
			startTimeIn = append(startTimeIn, startTime)
		}
	}
	if in.EndTime != nil && len(strings.TrimSpace(*in.EndTime)) > 0 {
		if endTime, err := modelx.ParseLocalDate(strings.TrimSpace(*in.EndTime)); err == nil {
			endTimeIn = append(endTimeIn, endTime)
		}
	}
	createdAtMap := make(map[string][]any)
	if len(startTimeIn) > 0 {
		createdAtMap["moreThanOrEqualTo"] = startTimeIn
	}
	if len(endTimeIn) > 0 {
		createdAtMap["lessThan"] = endTimeIn
	}
	filterMap[model.BlogPostColumns.PostDate] = createdAtMap

	if isAll {
		ret, _, err := l.svcCtx.Post.GetList(l.ctx, model.M{
			"statistic": "1",
		}, 5, filterMap)
		if err != nil {
			// return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
			logx.Errorw("BackendGetPostList GetList statistic err", logx.Field("detail", err.Error()))
		} else if len(ret) > 0 {
			statusStatisticMap := make(map[uint32]uint32)
			var total int
			for _, v := range ret {
				switch int32(v.PostStatus) {
				case globalkey.PostStatusPending:
					statusStatisticMap[2] += uint32(v.MenuOrder)
				case globalkey.PostStatusPublish, globalkey.PostStatusPrivate, globalkey.PostStatusFuture:
					statusStatisticMap[3] += uint32(v.MenuOrder)
				case globalkey.PostStatusRejected:
					statusStatisticMap[4] += uint32(v.MenuOrder)
				case globalkey.PostStatusDraft, globalkey.PostStatusAutoDraft:
					statusStatisticMap[5] += uint32(v.MenuOrder)
				case globalkey.PostStatusTrash:
					statusStatisticMap[6] += uint32(v.MenuOrder)
				}
				if v.PostStatus != uint16(globalkey.PostStatusTrash) {
					total += v.MenuOrder
				}
			}
			statusStatisticMap[1] = uint32(total)
			resp.StatusStatisticMap = statusStatisticMap
		}
	}

	if !statusIsSet {
		statusIn := modelx.UniqueInterfaces(stati, 32)
		if len(statusIn) > 0 {
			statusMap["in"] = statusIn
		}
		if in.Status != nil {
			statusMap["equalTo"] = []any{*in.Status}
		}
		filterMap[model.BlogPostColumns.PostStatus] = statusMap
	}

	var search string
	if in.Search != nil && len(strings.TrimSpace(*in.Search)) > 0 {
		search = strings.TrimSpace(*in.Search)
	}
	var orderBy string
	if len(orderBys) > 0 {
		orderBy = strings.Join(orderBys, ",")
	}
	res, count, err := l.svcCtx.Post.GetList(l.ctx, model.M{
		"page":     page,
		"pageSize": pageSize,
		"search":   search,
		"orderBy":  orderBy,
	}, withCount, filterMap)
	if err != nil {
		return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	if restricted {
		count = preCount
	}
	resp.Total = uint64(count)

	if len(res) > 0 {
		var pids []any
		for _, v := range res {
			pids = append(pids, v.ID)
		}

		idDetailMap := make(map[uint64]*model.BlogPostDetail)
		if in.WithContent != nil && *in.WithContent > 0 {
			subFilterMap := make(map[string]map[string][]any)
			subFilterMap[model.BlogPostDetailColumns.PostID] = map[string][]any{
				"in": pids,
			}
			list, _, err := l.svcCtx.Post.GetDetailList(l.ctx, model.M{
				"orderBy": model.BlogPostDetailColumns.DetailID + " DESC",
			}, -1, subFilterMap)
			if err != nil {
				return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
			}

			if len(list) > 0 {
				for _, v := range list {
					if _, ok := idDetailMap[v.PostID]; !ok {
						idDetailMap[v.PostID] = v
					}
				}
			}
		}

		idMetumMap := make(map[uint64][]*model.BlogPostMetum)
		if in.WithMeta != nil && *in.WithMeta > 0 {
			subFilterMap := make(map[string]map[string][]any)
			subFilterMap[model.BlogPostMetumColumns.PostID] = map[string][]any{
				"in": pids,
			}
			subFilterMap[model.BlogPostMetumColumns.MetaKey] = map[string][]any{
				"in": {
					"_thumbnail_id",
					"_thumbnail_url",
					"_photo_count",
					"_view_count",
					"_comment_count",
					"_forward_count",
					"_future_status",
					"_pending_status",
					"_sticky",
				},
			}
			list, _, err := l.svcCtx.Post.GetMeta(l.ctx, model.M{}, -1, subFilterMap)
			if err != nil {
				return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
			}

			if len(list) > 0 {
				for _, v := range list {
					idMetumMap[v.PostID] = append(idMetumMap[v.PostID], v)
				}
			}
		}

		idMediaMap := make(map[uint64][]*blog.TmpMediaInfo)
		if in.WithMedia != nil && *in.WithMedia > 0 {
			subFilterMap := make(map[string]map[string][]any)
			subFilterMap[model.BlogAttachmentRelationshipColumns.ObjectID] = map[string][]any{
				"in": pids,
			}
			subFilterMap[model.BlogAttachmentRelationshipColumns.ObjectType] = map[string][]any{
				"equalTo": {globalkey.AttachmentLogTypePost},
			}
			subFilterMap[model.BlogAttachmentRelationshipColumns.AttachmentThumbnail] = map[string][]any{
				"equalTo": {1},
			}
			list, _, err := l.svcCtx.Post.GetMedia(l.ctx, model.M{}, -1, subFilterMap)
			if err != nil {
				return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
			}

			if len(list) > 0 {
				for _, v := range list {
					idMediaMap[v.ObjectID] = append(idMediaMap[v.ObjectID], &blog.TmpMediaInfo{
						Uid:       &v.AttachmentRelationshipID,
						Id:        pointy.GetPointer(strconv.FormatInt(int64(v.AttachmentID), 10)),
						Thumbnail: pointy.GetPointer(uint32(v.AttachmentThumbnail)),
						Order:     pointy.GetPointer(uint32(v.AttachmentOrder)),
						Uri:       &v.AttachmentSource,
						Url:       &v.AttachmentImageURL,
						AltText:   &v.AttachmentImageAlt,
						MetaData:  &v.AttachmentImageMeta,
					})
				}
			}
		}

		idCategoryMap := make(map[uint64][]*blog.TermInfo)
		idTagMap := make(map[uint64][]*blog.TermInfo)
		if (in.WithCategory != nil && *in.WithCategory > 0) || (in.WithTag != nil && *in.WithTag > 0) {
			subFilterMap := make(map[string]map[string][]any)
			subFilterMap[model.BlogTermRelationshipColumns.ObjectID] = map[string][]any{
				"in": pids,
			}
			subFilterMap[model.BlogTermRelationshipColumns.ObjectType] = map[string][]any{
				"equalTo": {globalkey.PostTypePost},
			}
			list, _, err := l.svcCtx.Term.GetRelationshipList(l.ctx, model.M{
				"select": model.BlogTermRelationshipColumns.ObjectID + "," + model.BlogTermRelationshipColumns.TermID,
			}, -1, subFilterMap)
			if err != nil {
				return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
			}

			if len(list) > 0 {
				tidMap := make(map[uint64][]uint64) // Map: model.BlogTerm.TermID => []model.BlogPost.ID
				var tids []any                      // []model.BlogTerm.TermID(已去重)
				for _, v := range list {
					if _, ok := tidMap[v.TermID]; !ok {
						tids = append(tids, v.TermID)
					}
					tidMap[v.TermID] = append(tidMap[v.TermID], v.ObjectID)
				}

				termFilterMap := make(map[string]map[string][]any)
				termFilterMap[model.BlogTermColumns.TermID] = map[string][]any{
					"in": tids,
				}
				terms, _, err := l.svcCtx.Term.GetList(l.ctx, model.M{}, -1, termFilterMap)
				if err != nil {
					return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
				}

				if len(terms) > 0 {
					for _, term := range terms {
						if oids, ok := tidMap[term.TermID]; ok {
							if term.Taxonomy == uint16(globalkey.TermTaxonomyCategory) { // 是分类
								if in.WithCategory != nil && *in.WithCategory > 0 {
									oidMap := make(map[uint64]struct{})
									for _, oid := range oids {
										if _, ok := oidMap[oid]; !ok {
											idCategoryMap[oid] = append(idCategoryMap[oid], &blog.TermInfo{
												Id:   &term.TermID,
												Name: &term.Name,
												Slug: &term.Slug,
											})
											oidMap[oid] = struct{}{}
										}
									}
								}
							} else if term.Taxonomy == uint16(globalkey.TermTaxonomyTag) { // 是分类
								if in.WithTag != nil && *in.WithTag > 0 {
									oidMap := make(map[uint64]struct{})
									for _, oid := range oids {
										if _, ok := oidMap[oid]; !ok {
											idTagMap[oid] = append(idTagMap[oid], &blog.TermInfo{
												Id:   &term.TermID,
												Name: &term.Name,
												Slug: &term.Slug,
											})
											oidMap[oid] = struct{}{}
										}
									}
								}
							}
						}
					}
				}
			}
		}

		for _, v := range res {
			var content *string
			var contentFiltered *string
			var meta []*blog.MetaInfo
			var media []*blog.TmpMediaInfo
			var commentCount int64
			var sticky int32
			var futureStatus int64
			var pendingStatus int64
			if detail, ok := idDetailMap[v.ID]; ok {
				content = pointy.GetPointer(detail.PostContent)
				contentFiltered = pointy.GetPointer(detail.PostContentFiltered)
			}
			if ms, ok := idMetumMap[v.ID]; ok {
				var mediaId *string
				var mediaUrl *string
				for _, m := range ms {
					key := strings.TrimSpace(m.MetaKey)
					if len(key) > 0 && m.MetaValue.Valid {
						val := m.MetaValue.String
						switch key {
						case "_thumbnail_id":
							mediaId = pointy.GetPointer(val)
						case "_thumbnail_url":
							mediaUrl = pointy.GetPointer(val)
						case "_photo_count":
							meta = append(meta, &blog.MetaInfo{
								Id:    &m.MetaID,
								Key:   pointy.GetPointer("photoCount"),
								Value: pointy.GetPointer(val),
							})
						case "_future_status":
							if i, err := strconv.ParseInt(val, 10, 32); err == nil {
								futureStatus = i
							}
						case "_pending_status":
							if i, err := strconv.ParseInt(val, 10, 32); err == nil {
								pendingStatus = i
								meta = append(meta, &blog.MetaInfo{
									Id:    &m.MetaID,
									Key:   pointy.GetPointer("pendingStatus"),
									Value: pointy.GetPointer(val),
								})
							}
						case "_view_count":
							meta = append(meta, &blog.MetaInfo{
								Id:    &m.MetaID,
								Key:   pointy.GetPointer("viewCount"),
								Value: pointy.GetPointer(val),
							})
						case "_comment_count":
							if i, err := strconv.ParseInt(val, 10, 32); err == nil {
								commentCount = i
							}
						case "_forward_count":
							meta = append(meta, &blog.MetaInfo{
								Id:    &m.MetaID,
								Key:   pointy.GetPointer("forwardCount"),
								Value: pointy.GetPointer(val),
							})
						case "_sticky":
							switch val {
							case "frontpage":
								sticky = globalkey.BlogStickyTypeFrontpage
							case "category":
								sticky = globalkey.BlogStickyTypeCategory
							case "tag":
								sticky = globalkey.BlogStickyTypeTag
							}
						default:
							meta = append(meta, &blog.MetaInfo{
								Id:    &m.MetaID,
								Key:   pointy.GetPointer(key),
								Value: pointy.GetPointer(val),
							})
						}
					}
				}
				if mediaUrl != nil {
					media = append(media, &blog.TmpMediaInfo{
						Id:        mediaId,
						Thumbnail: pointy.GetPointer(uint32(1)),
						Url:       mediaUrl,
					})
				}
			}
			if ms, ok := idMediaMap[v.ID]; ok {
				media = ms[:]
			}
			status := int32(v.PostStatus)
			if pendingStatus == 0 && futureStatus > 0 {
				status = int32(futureStatus)
			}
			finalStatus := int32(v.PostStatus)
			if futureStatus > 0 {
				finalStatus = int32(futureStatus)
			} else if pendingStatus > 0 {
				finalStatus = int32(pendingStatus)
			}
			var password *string
			visibility := globalkey.PostVisibilityPublic
			switch finalStatus {
			case globalkey.PostStatusPublish:
				if len(v.PostPassword) > 0 {
					password = pointy.GetPointer(v.PostPassword)
					visibility = globalkey.PostVisibilityPasswordProtected
				}
			case globalkey.PostStatusPrivate:
				visibility = globalkey.PostVisibilityPrivate
			}

			var categories []*blog.TermInfo
			if terms, ok := idCategoryMap[v.ID]; ok {
				categories = terms[:]
			}

			var tags []*blog.TermInfo
			if terms, ok := idTagMap[v.ID]; ok {
				tags = terms[:]
			}

			resp.List = append(resp.List, &blog.PostInfo{
				Id:              &v.ID,
				Title:           &v.PostTitle,
				Slug:            &v.PostName,
				Content:         content,
				ContentFiltered: contentFiltered,
				Excerpt:         &v.PostExcerpt,
				Status:          pointy.GetPointer(uint32(status)),
				CommentStatus:   pointy.GetPointer(uint32(v.CommentStatus)),
				Sort:            pointy.GetPointer(uint32(v.MenuOrder)),
				Uri:             &v.GUID,
				AuthorId:        &v.PostAuthorID,
				ParentId:        &v.PostParentID,
				Visibility:      pointy.GetPointer(visibility),
				Password:        password,
				Sticky:          pointy.GetPointer(uint32(sticky)),
				CreatedAt:       pointy.GetPointer(modelx.FormatTime(v.PostDate, "")),
				UpdatedAt:       pointy.GetPointer(modelx.FormatTime(v.PostModifiedDate, "")),
				DeletedAt:       pointy.GetPointer(modelx.FormatNullDotTime(v.DeletedAt, "")),
				CommentCount:    pointy.GetPointer(uint32(commentCount)),
				Meta:            meta,
				Media:           media,
				Categories:      categories,
				Tags:            tags,
			})
		}
	}

	return resp, nil
}
